[id].vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <!--
  2. * @Author: LiZhiWei
  3. * @Date: 2026-01-22 14:08:24
  4. * @LastEditors: LiZhiWei
  5. * @LastEditTime: 2026-01-23 11:26:02
  6. * @Description: 解决方案详情页
  7. -->
  8. <template>
  9. <div v-if="solution" class="solution-page">
  10. <!-- Hero Section -->
  11. <section class="solution-hero">
  12. <div
  13. class="landing-container flex flex-col items-center justify-center h-full text-center lt-sm:px-32px"
  14. >
  15. <h1 class="hero-title pf-sc-semibold">{{ solution.title }}</h1>
  16. <p class="hero-subtitle pf-sc-semibold">{{ solution.subTitle }}</p>
  17. <p class="hero-desc pf-sc-regular">{{ solution.description }}</p>
  18. <button class="hero-btn btn-primary pf-sc-semibold" @click="openConsultation">
  19. 立即免费咨询
  20. </button>
  21. </div>
  22. </section>
  23. <!-- Core Pain Points -->
  24. <section
  25. ref="painPointsRef"
  26. class="section-container bg-white transition-all duration-1000 ease-out"
  27. :class="[isPainPointsVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-50px']"
  28. >
  29. <div class="landing-container lt-sm:px-32px">
  30. <h2 class="section-title pf-sc-semibold">{{ solution.corePoints.title }}</h2>
  31. <div class="grid grid-cols-3 gap-25px lt-sm:grid-cols-1">
  32. <div
  33. v-for="(item, index) in solution.corePoints.points"
  34. :key="index"
  35. class="pain-card group"
  36. >
  37. <i :class="[item.icon, 'pain-icon']"></i>
  38. <h3 class="pain-title pf-sc-semibold">{{ item.point }}</h3>
  39. <p class="pain-desc pf-sc-regular">{{ item.pointDesc }}</p>
  40. </div>
  41. </div>
  42. </div>
  43. </section>
  44. <!-- Core Functions -->
  45. <section
  46. ref="functionsRef"
  47. class="section-container bg-#F6F8FD transition-all duration-1000 ease-out"
  48. :class="[isFunctionsVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-50px']"
  49. >
  50. <div class="landing-container lt-sm:px-32px">
  51. <h2 class="section-title pf-sc-semibold">{{ solution.coreFunctions.title }}</h2>
  52. <div class="grid grid-cols-1 sm:grid-cols-2 gap-24px mt-60px">
  53. <div
  54. v-for="(item, index) in solution.coreFunctions.function"
  55. :key="index"
  56. class="function-card"
  57. >
  58. <div class="function-card-header">
  59. <img src="~/assets/icons/function.svg" class="function-icon" alt="icon" />
  60. <h3 class="function-title pf-sc-semibold">{{ item.name }}</h3>
  61. </div>
  62. <p class="function-desc pf-sc-regular">{{ item.funcDesc }}</p>
  63. <div class="function-tags">
  64. <span
  65. v-for="(tag, tIndex) in item.funcPoints"
  66. :key="tIndex"
  67. class="function-tag pf-sc-medium"
  68. >
  69. <i class="i-custom-check-one wh-15px lt-sm:wh-24px"></i>
  70. {{ tag }}
  71. </span>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. </section>
  77. <!-- Core Effects -->
  78. <section
  79. ref="effectsRef"
  80. class="section-container bg-white transition-all duration-1000 ease-out"
  81. :class="[isEffectsVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-50px']"
  82. >
  83. <div class="landing-container">
  84. <h2 class="section-title pf-sc-semibold">{{ solution.coreEffects.title }}</h2>
  85. <div
  86. class="flex justify-between mt-80px gap-y-40px lt-sm:mt-72px lt-sm:grid lt-sm:grid-cols-2!"
  87. >
  88. <div
  89. v-for="(item, index) in solution.coreEffects.effect"
  90. :key="index"
  91. class="effect-item flex-1 text-center"
  92. >
  93. <div class="effect-percent d-din-pro-700-bold">{{ item.percent }}</div>
  94. <p class="effect-name pf-sc-regular">{{ item.name }}</p>
  95. </div>
  96. </div>
  97. </div>
  98. </section>
  99. <!-- Typical Cases -->
  100. <section
  101. v-if="currentCase"
  102. ref="casesRef"
  103. class="section-container solution-case transition-all duration-1000 ease-out"
  104. :class="[isCasesVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-50px']"
  105. >
  106. <div class="landing-container lt-sm:px-32px">
  107. <h2 class="section-title pf-sc-semibold">{{ solution.typicalCases.title }}</h2>
  108. <div class="mt-60px lt-sm:mt-48px">
  109. <div class="case-card">
  110. <div class="case-img-wrapper">
  111. <img :src="currentCase.caseCover" class="case-img" :alt="currentCase.caseName" />
  112. </div>
  113. <div class="case-content">
  114. <div class="case-header">
  115. <h3 class="case-name pf-sc-semibold">{{ currentCase.caseName }}</h3>
  116. <div v-if="(solution.typicalCases.cases?.length ?? 0) > 1" class="case-nav-btns">
  117. <!-- PC Prev -->
  118. <i
  119. class="i-custom-arrow-circle-left wh-32px transition-colors lt-sm:hidden"
  120. :class="
  121. activeCaseIndex === 0
  122. ? 'cursor-not-allowed opacity-50'
  123. : 'cursor-pointer hover:i-custom-arrow-circle-left-active'
  124. "
  125. @click="prevCase"
  126. ></i>
  127. <!-- Mobile Prev -->
  128. <i
  129. class="i-custom-button-previous-mobile wh-32px transition-all shrink-0 hidden lt-sm:block"
  130. :class="[
  131. activeCaseIndex === 0
  132. ? 'cursor-not-allowed i-custom-button-previous-mobile-disabled'
  133. : 'cursor-pointer active:i-custom-button-previous-mobile-active',
  134. ]"
  135. @click="prevCase"
  136. ></i>
  137. <!-- PC Next -->
  138. <i
  139. class="i-custom-arrow-circle-right wh-32px ml-12px transition-colors lt-sm:hidden"
  140. :class="
  141. activeCaseIndex === solution.typicalCases.cases.length - 1
  142. ? 'cursor-not-allowed opacity-50'
  143. : 'cursor-pointer hover:i-custom-arrow-circle-right-active'
  144. "
  145. @click="nextCase"
  146. ></i>
  147. <!-- Mobile Next -->
  148. <i
  149. class="i-custom-button-next-mobile wh-32px ml-12px transition-all shrink-0 hidden lt-sm:block"
  150. :class="[
  151. activeCaseIndex === solution.typicalCases.cases.length - 1
  152. ? 'cursor-not-allowed i-custom-button-next-mobile-disabled'
  153. : 'cursor-pointer active:i-custom-button-next-mobile-active',
  154. ]"
  155. @click="nextCase"
  156. ></i>
  157. </div>
  158. </div>
  159. <div class="case-section">
  160. <h4 class="case-subtitle pf-sc-semibold">客户痛点</h4>
  161. <div class="case-text pf-sc-regular">{{ currentCase.casePainPoint }}</div>
  162. </div>
  163. <div
  164. class="case-section mt-24px bg-#F6F8FD p-16px rounded-8px lt-sm:mt-24px lt-sm:p-24px lt-sm:rounded-8px"
  165. >
  166. <h4 class="case-subtitle pf-sc-semibold">落地效果</h4>
  167. <div class="case-text pf-sc-regular">
  168. <div v-for="(effect, eIndex) in currentCase.caseEffect" :key="eIndex">
  169. {{ effect }}
  170. </div>
  171. </div>
  172. </div>
  173. </div>
  174. </div>
  175. </div>
  176. </div>
  177. </section>
  178. <!-- CTA -->
  179. <section-cta />
  180. </div>
  181. </template>
  182. <script setup lang="ts">
  183. import { solutionPoints } from '@/constants/common'
  184. const route = useRoute()
  185. const { openConsultation } = useConsultation()
  186. const solution = computed(() => solutionPoints.find((item) => item.id === route.params.id))
  187. const painPointsRef = ref<HTMLElement | null>(null)
  188. const { isVisible: isPainPointsVisible } = useScrollReveal(painPointsRef)
  189. const functionsRef = ref<HTMLElement | null>(null)
  190. const { isVisible: isFunctionsVisible } = useScrollReveal(functionsRef)
  191. const effectsRef = ref<HTMLElement | null>(null)
  192. const { isVisible: isEffectsVisible } = useScrollReveal(effectsRef)
  193. const casesRef = ref<HTMLElement | null>(null)
  194. const { isVisible: isCasesVisible } = useScrollReveal(casesRef)
  195. const activeCaseIndex = ref(0)
  196. const currentCase = computed(() => {
  197. if (!solution.value?.typicalCases?.cases?.length) return null
  198. return solution.value.typicalCases.cases[activeCaseIndex.value]
  199. })
  200. const nextCase = () => {
  201. if (!solution.value?.typicalCases?.cases) return
  202. if (activeCaseIndex.value < solution.value.typicalCases.cases.length - 1) {
  203. activeCaseIndex.value++
  204. }
  205. }
  206. const prevCase = () => {
  207. if (activeCaseIndex.value > 0) {
  208. activeCaseIndex.value--
  209. }
  210. }
  211. useSeoMeta({
  212. title: () => `${solution.value?.title || '解决方案'} - 绘家科技`,
  213. description: () =>
  214. solution.value?.description ||
  215. '绘家科技致力于为物业管理公司提供数字化转型解决方案,提升服务品质与业主满意度。',
  216. keywords: () =>
  217. `${solution.value?.title},智慧物业,解决方案,绘家科技,${solution.value?.corePoints.points.map((p) => p.point).join(',')}`,
  218. ogTitle: () => `${solution.value?.title || '解决方案'} - 绘家科技`,
  219. ogDescription: () =>
  220. solution.value?.description ||
  221. '绘家科技致力于为物业管理公司提供数字化转型解决方案,提升服务品质与业主满意度。',
  222. })
  223. </script>
  224. <style scoped lang="scss">
  225. .section-container {
  226. @apply py-120px lt-sm:py-120px;
  227. }
  228. .section-title {
  229. @apply font-s-36px text-#000000 text-center lh-60px mb-40px lt-sm:font-s-48px lt-sm:mb-40px;
  230. }
  231. /* Hero */
  232. .solution-hero {
  233. @apply w-full h-600px relative bg-cover bg-center bg-no-repeat;
  234. @apply lt-sm:h-auto lt-sm:py-160px;
  235. background-image: url('~/assets/images/solution-bg.png');
  236. @screen lt-sm {
  237. background-image: url('~/assets/images/solution-mobile-bg.png');
  238. }
  239. }
  240. .hero-title {
  241. @apply font-s-48px text-#000000;
  242. @apply lt-sm:font-s-56px lt-sm:px-20px;
  243. }
  244. .hero-subtitle {
  245. @apply font-s-38px text-#000000 mb-16px;
  246. @apply lt-sm:font-s-38px lt-sm:px-20px;
  247. }
  248. .hero-desc {
  249. @apply font-s-18px text-#091221/70;
  250. @apply lt-sm:font-s-26px lt-sm:px-32px;
  251. }
  252. .hero-btn {
  253. @apply w-162px h-56px rounded-8px mt-36px font-s-18px text-white hover:opacity-80 transition-colors cursor-pointer;
  254. @apply lt-sm:w-224px lt-sm:h-71px lt-sm:rounded-8px lt-sm:font-s-28px;
  255. }
  256. /* Pain Points */
  257. .pain-card {
  258. @apply bg-white rounded-16px p-40px border border-#E2E8F0 hover:shadow-md transition-all duration-300 text-center flex flex-col items-center;
  259. @apply lt-sm:rounded-16px lt-sm:p-40px;
  260. }
  261. .pain-icon {
  262. @apply wh-64px mb-16px;
  263. @apply lt-sm:wh-69px;
  264. }
  265. .pain-title {
  266. @apply font-s-18px text-#091221 mb-16px;
  267. @apply lt-sm:font-s-32px;
  268. }
  269. .pain-desc {
  270. @apply font-s-14px text-#091221/70 text-center;
  271. @apply lt-sm:font-s-24px;
  272. }
  273. /* Functions */
  274. .function-card {
  275. @apply bg-gradient-to-b from-[#E5E9F5] to-[#EFF2FB] rounded-16px p-40px transition-all duration-300;
  276. @apply lt-sm:rounded-16px lt-sm:p-40px;
  277. }
  278. .function-card-header {
  279. @apply flex items-start flex-col gap-12px mb-12px;
  280. @apply lt-sm:gap-24px lt-sm:mb-24px;
  281. }
  282. .function-icon {
  283. @apply wh-44px;
  284. @apply lt-sm:wh-78px;
  285. }
  286. .function-title {
  287. @apply font-s-18px text-#091221;
  288. @apply lt-sm:font-s-32px;
  289. }
  290. .function-desc {
  291. @apply font-s-14px text-#091221/70 mb-12px lh-24px;
  292. @apply lt-sm:font-s-24px lt-sm:lh-normal lt-sm:mb-24px;
  293. }
  294. .function-tags {
  295. @apply flex flex-wrap gap-26px;
  296. @apply lt-sm:gap-24px;
  297. }
  298. .function-tag {
  299. @apply flex items-center font-s-14px text-#091221 gap-8px;
  300. @apply lt-sm:font-s-24px lt-sm:gap-8px;
  301. }
  302. /* Effects */
  303. .effect-percent {
  304. @apply font-s-48px text-#0F67F8 lt-sm:font-s-56px;
  305. }
  306. .effect-name {
  307. @apply font-s-16px text-#384146;
  308. }
  309. .solution-case {
  310. @apply bg-cover bg-center bg-no-repeat;
  311. background-image: url('~/assets/images/case-bg.png');
  312. }
  313. /* Typical Cases */
  314. .case-card {
  315. @apply bg-white rounded-16px p-24px flex gap-40px items-stretch shadow-sm;
  316. @apply lt-sm:flex-col lt-sm:p-32px lt-sm:gap-42px lt-sm:rounded-16px;
  317. }
  318. .case-img-wrapper {
  319. @apply w-542px h-342px rounded-8px overflow-hidden flex-shrink-0;
  320. @apply lt-sm:w-full lt-sm:h-320px lt-sm:rounded-12px;
  321. }
  322. .case-img {
  323. @apply w-full h-full object-fill;
  324. }
  325. .case-content {
  326. @apply flex-1 py-10px flex flex-col justify-center;
  327. }
  328. .case-header {
  329. @apply flex justify-between items-center mb-30px;
  330. @apply lt-sm:mb-24px;
  331. }
  332. .case-name {
  333. @apply font-s-22px text-#091221;
  334. }
  335. .case-nav-btns {
  336. @apply flex items-center;
  337. }
  338. .case-subtitle {
  339. @apply font-s-18px text-#091221 mb-12px;
  340. @apply lt-sm:mb-12px;
  341. }
  342. .case-text {
  343. @apply font-s-14px text-#091221/70 lh-24px;
  344. @apply lt-sm:font-s-24px lt-sm:lh-normal;
  345. }
  346. </style>